﻿using System;
using System.Collections.Generic;
using System.IdentityModel.Claims;
using System.Net;
using System.Text;
using System.Threading;
using IdentityAtRest.Utility.Identity;

namespace IdentityAtRest.Utility.Identity
{
    public static class AuthenticationHelper
    {

        public const string AUTH_TOKEN_HEADER_NAME = "x-hsi-auth";

        public static bool SetCurrentUserFromAuthToken(string authToken)
        {
            if (String.IsNullOrEmpty(authToken) == false)
            {
                IList<IClaim> claims = UnpackAuthToken(authToken);
                Thread.CurrentPrincipal = new ClaimsPrincipal(new ClaimsIdentity("", claims));
                return true;
            }
            return false;
        }

        public static IList<IClaim> UnpackAuthToken(string authToken)
        {
            var results = new List<IClaim>();

            string[] claims = authToken.Split('|');
            foreach (string claimStr in claims)
            {
                string[] claim = claimStr.Split('=');
                results.Add(new Claim { ClaimType = claim[0], Value = claim[1], ValueType = "string" });
            }
            return results;
        }

        public static string PackClaimsPrincipalAsAuthToken(this IClaimsPrincipal claimsPrincipal)
        {
            if (claimsPrincipal.ClaimsIdentity == null)
                throw new InvalidOperationException("Claims identity not set in IPrincipal");
            var sb = new StringBuilder();
            foreach ( IClaim claim in claimsPrincipal.ClaimsIdentity.Claims)
            {
                string token = claim.ClaimType + "=" + claim.Value;
                if (sb.Length == 0)
                    sb.Append(token);
                else
                    sb.AppendFormat("|{0}", token);
            }
            return sb.ToString();
        }

        public static string GetAuthTokenForCurrentUser()
        {
            var principal = Thread.CurrentPrincipal as IClaimsPrincipal;
            if (principal != null)
                return principal.PackClaimsPrincipalAsAuthToken();
            return String.Empty;
        }

        public static void ImpersonateUser(IList<IClaim> impersonatedClaims)
        {
            if (impersonatedClaims == null)
                impersonatedClaims = new List<IClaim>();

            var email = impersonatedClaims.GetClaimValue<string>(ClaimTypes.Email);
            if (String.IsNullOrEmpty(email))
                email = "unknown_user_email@unknown.com";

            var id = new ClaimsIdentity(email, impersonatedClaims);
            Thread.CurrentPrincipal = new ClaimsPrincipal(id);
        }

        public static void ImpersonateUser(params IClaim[] impersonatedClaims)
        {
            var claims = new List<IClaim>();
            if (impersonatedClaims != null)
                claims.AddRange(impersonatedClaims);
            ImpersonateUser(claims);
        }

        public static void ImpersonateSuperUser()
        {
            ImpersonateUser(new Claim(CustomClaimTypes.SuperUser, true));
        }

        public static void ImpersonateUserInRoles(params string[] roles)
        {
            var claims = new List<IClaim>();
            foreach (String role in roles)
            {
                claims.AddNew(CustomClaimTypes.Role, role);
            }
            ImpersonateUser(claims);
        }

        public static Claim AddNew(this IList<IClaim> claims,string claimType,string claimValue)
        {
            var newClaim = new Claim(claimType,claimValue);
            claims.Add(newClaim);
            return newClaim;
        }

        public static Claim AddNew(this IList<IClaim> claims,string claimType,object claimValue)
        {
            return AddNew(claims, claimType, claimValue != null ? claimValue.ToString() : String.Empty);
        }

        public static T GetClaimValue<T>(this IList<IClaim> claims,string claimType)
        {
            foreach (IClaim claim in claims)
            {
                if (String.Compare(claim.ClaimType, claimType,true) == 0)
                {
                    return TypeNormalizer.EnsureType<T>(claim.Value, default(T));
                }
            }
            return default(T);
        }

        public static bool ClaimEquals<T>(this IList<IClaim> claims,string claimType,T comparisonValue)
        {
            return EvaluateClaim<T>(claims, claimType, x => x.Equals(comparisonValue) );
        }

        public static bool ClaimEqualsAny(this IList<IClaim> claims,string claimType,string claimValue)
        {
            foreach (IClaim claim in claims)
            {
                if (String.Compare(claim.ClaimType,claimType,true) == 0)
                {
                    if (claim.Value.Equals(claimValue, StringComparison.InvariantCultureIgnoreCase))
                        return true;
                }
            }
            return false;
        }

        public static bool ClaimEqualsAny<T>(this IList<IClaim> claims,string claimType,T comparisonValue)
        {
            foreach (IClaim claim in claims)
            {
                if (String.Compare(claim.ClaimType,claimType,true) == 0)
                {
                    if (claim.Value.Equals(comparisonValue))
                        return true;
                }
            }
            return false;
        }

        public static bool EvaluateClaim<T>(this IList<IClaim> claims,string claimType,Func<T,bool> eval)
        {
            var claimValue = claims.GetClaimValue<T>(claimType);
            return eval(claimValue);
        }

        public static bool HasClaim(this IList<IClaim> claims,string claimType)
        {
            foreach (IClaim claim in claims)
            {
                if (claimType.Equals(claim.ClaimType, StringComparison.CurrentCultureIgnoreCase))
                    return true;

            }
            return true;
        }

        public static NetworkCredential ExtractCredentialFromUserClaims()
        {
            var currentUser = Thread.CurrentPrincipal as IClaimsPrincipal;

            if (currentUser == null)
                return null;

            var credential = new NetworkCredential
                                 {
                                     UserName = currentUser.GetClaimValue<string>(ClaimTypes.Email),
                                     Password = currentUser.GetClaimValue<string>(CustomClaimTypes.Password)
                                 };

            return credential;
        }
      
    }
}